﻿using System;
using System.Collections.Generic;
using TLang.Values;
using System.Linq;

namespace TLang
{
    using Ast;

    public class Binder
    {
        public static void Define(Node pattern, Value value, Scope env)
        {
            if (pattern is Name)
            {
                String id = ((Name)pattern).Id;
                Value v = env.LookupLocal(id);
                if (v != null)
                {
                    Util.Abort(pattern, "trying to redefine name: " + id);
                }
                else
                {
                    env.PutValue(id, value);
                }
            }
            else if (pattern is RecordLiteral)
            {
                if (value is RecordType)
                {
                    Map<String, Node> elms1 = ((RecordLiteral)pattern).Map;
                    Scope elms2 = ((RecordType)value).properties;
                    if (elms1.Keys.Count == elms2.Keys.Count && elms2.Keys.All(a => elms1.Keys.Contains(a)))
                    {
                        foreach (String k1 in elms1.Keys)
                        {
                            Define(elms1.Get(k1), elms2.LookupLocal(k1), env);
                        }
                    }
                    else
                    {
                        Util.Abort(pattern, "define with records of different attributes: " +
                                elms1.Keys + " v.s. " + elms2.Keys);
                    }
                }
                else
                {
                    Util.Abort(pattern, "define with incompatible types: record and " + value);
                }
            }
            else if (pattern is VectorLiteral)
            {
                if (value is Vector)
                {
                    List<Node> elms1 = ((VectorLiteral)pattern).elements;
                    List<Value> elms2 = ((Vector)value).Values;
                    if (elms1.Count == elms2.Count)
                    {
                        for (int i = 0; i < elms1.Count; i++)
                        {
                            Define(elms1[i], elms2[i], env);
                        }
                    }
                    else
                    {
                        Util.Abort(pattern,
                                "define with vectors of different sizes: " + elms1.Count + " v.s. " + elms2.Count);
                    }
                }
                else
                {
                    Util.Abort(pattern, "define with incompatible types: vector and " + value);
                }
            }
            else
            {
                Util.Abort(pattern, "unsupported pattern of define: " + pattern);
            }
        }


        public static void Assign(Node pattern, Value value, Scope env)
        {
            if (pattern is Name)
            {
                String id = ((Name)pattern).Id;
                string[] items = id.Split(Constants.Atribute_Access_Chars);
                // (set! aa:0 10) form
                if (items.Length == 2)
                {
                    id = items[0];
                    string key = items[1];
                    Scope d = env.FindDefiningScope(id);

                    if (d == null)
                    {
                        Util.Abort(pattern, "assigned name was not defined: " + id);
                    }
                    else
                    {
                        Value vector = d.LookupLocal(id);
                        if (vector is Vector vectorValue)
                        {
                            int i = -1;
                            if (!int.TryParse(key, out i))
                            {
                                Util.Abort(pattern, key + " is not a number");
                            }
                            vectorValue.Set(i, value);
                        }
                        else if (vector is RecordType recordType)
                        {
                            recordType.properties.PutValue(key, value);
                        }
                    }
                }
                else if(items.Length == 1)
                {
                    Scope d = env.FindDefiningScope(id);

                    if (d == null)
                    {
                        Util.Abort(pattern, "assigned name was not defined: " + id);
                    }
                    else
                    {
                        d.PutValue(id, value);
                    }
                }
                else
                {
                    Util.Abort(pattern, "too many items: " + id);
                }
            }
            else if (pattern is Subscript)
            {
                ((Subscript)pattern).set(value, env);
            }
            else if (pattern is Attr)
            {
                ((Attr)pattern).set(value, env);
            }
            else if (pattern is RecordLiteral)
            {
                if (value is RecordType)
                {
                    Map<String, Node> elms1 = ((RecordLiteral)pattern).Map;
                    Scope elms2 = ((RecordType)value).properties;
                    if (elms1.Keys.Count == elms2.Keys.Count && elms1.Keys.All(a => elms2.Keys.Contains(a)))
                    {
                        foreach (String k1 in elms1.Keys)
                        {
                            Assign(elms1.Get(k1), elms2.LookupLocal(k1), env);
                        }
                    }
                    else
                    {
                        Util.Abort(pattern, "assign with records of different attributes: " +
                                elms1.Keys + " v.s. " + elms2.Keys);
                    }
                }
                else
                {
                    Util.Abort(pattern, "assign with incompatible types: record and " + value);
                }
            }
            else if (pattern is VectorLiteral)
            {
                if (value is Vector)
                {
                    List<Node> elms1 = ((VectorLiteral)pattern).elements;
                    List<Value> elms2 = ((Vector)value).Values;
                    if (elms1.Count == elms2.Count)
                    {
                        for (int i = 0; i < elms1.Count; i++)
                        {
                            Assign(elms1[i], elms2[i], env);
                        }
                    }
                    else
                    {
                        Util.Abort(pattern, "assign vectors of different sizes: " + elms1.Count + " v.s. " + elms2.Count);
                    }
                }
                else
                {
                    Util.Abort(pattern, "assign incompatible types: vector and " + value);
                }
            }
            else
            {
                Util.Abort(pattern, "unsupported pattern of assign: " + pattern);
            }
        }


        public static void CheckDup(Node pattern)
        {
            CheckDup1(pattern, new HashSet<String>());
        }


        private static void CheckDup1(Node pattern, ISet<String> seen)
        {

            if (pattern is Name)
            {
                String id = ((Name)pattern).Id;
                if (seen.Contains(id))
                {
                    Util.Abort(pattern, "duplicated name found in pattern: " + pattern);
                }
                else
                {
                    seen.Add(id);
                }
            }
            else if (pattern is RecordLiteral)
            {
                foreach (Node v in ((RecordLiteral)pattern).Map.Values)
                {
                    CheckDup1(v, seen);
                }
            }
            else if (pattern is VectorLiteral)
            {
                foreach (Node v in ((VectorLiteral)pattern).elements)
                {
                    CheckDup1(v, seen);
                }
            }
        }
    }
}
